
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.h"

#define POST_LENGTH 4
#define POST_STRING "POST"
#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

static int isFinishedBuffering(Log *context);
static int isLoggableData(Log *context);
static void switchToLogging(Log *context, char *buf, int length);
static void switchToProxying(Log *context);
static void bufferData(Log *context, char* buf, int length);
static void logData(Log *context, char* buf, int length);

static void connectionString(int client, int server, char *buf, int length);

Log * LOG_init(int client, int server, int postOnly) {
  Log *log         = (Log*)malloc(sizeof(Log));
  log->bufferIndex = 0;
  log->status      = (postOnly ? BUFFERING : PRELOGGING);
  log->logFile     = NULL;

  connectionString(client, server, log->name, sizeof(log->name));

  return log;
}

void LOG_log(Log *context, char* buf, int length) {
  switch (context->status) {
  case BUFFERING:  bufferData(context, buf, length);      break;
  case LOGGING:    logData(context, buf, length);         break;
  case PRELOGGING: switchToLogging(context, buf, length); break;
  case PROXYING:                                          break;
  default:
    fprintf(stderr, "ASSERT - Unknown LOG state: %d\n", context->status);
    exit(1);
  }
}

void LOG_flush(Log *context) {
  if (context->logFile != NULL)
    fflush(context->logFile);
}

static void connectionString(int client, int server, char *buf, int length) {
  char clientName[512];
  char serverName[512];

  struct sockaddr_in clientAddr;
  struct sockaddr_in serverAddr;

  int size;

  size = sizeof(clientAddr);
  if (getpeername(client, (struct sockaddr*)&clientAddr, &size) < 0) {
    perror("ASSERT - c getpeername failed.");
    exit(1);
  }

  size = sizeof(serverAddr);
  if (getpeername(server, (struct sockaddr*)&serverAddr, &size) < 0) {
    perror("ASSERT - s getpeername failed.");
    exit(1);
  }

  strcpy(clientName, inet_ntoa(clientAddr.sin_addr));
  strcpy(serverName, inet_ntoa(serverAddr.sin_addr));
  
  snprintf(buf, length, "%s.%d-%s.%d", 
	  clientName, ntohs(clientAddr.sin_port), 
	  serverName, ntohs(serverAddr.sin_port));
}

static int isFinishedBuffering(Log *context) {
  return (context->bufferIndex == POST_LENGTH);
}

static int isLoggableData(Log *context) {
  context->buffer[context->bufferIndex] = '\0';
  return (strcmp(context->buffer, POST_STRING) == 0);
}

static void switchToLogging(Log *context, char *buf, int length) {
  context->status  = LOGGING;
  context->logFile = fopen(context->name, "w");
  logData(context, buf, length);
  printf("Intercepted Connection: %s [Logging]\n", context->name);
}

static void switchToProxying(Log *context) {
  context->status = PROXYING;
  printf("Intercepted Connection: %s [Proxying]\n", context->name);
}

static void bufferData(Log *context, char* buf, int length) {
  int bytesToBuffer = MIN(POST_LENGTH - context->bufferIndex, length);
  memcpy(context->buffer, buf, bytesToBuffer);
  (context->bufferIndex) += bytesToBuffer;

  if (isFinishedBuffering(context)) {
    if (isLoggableData(context)) switchToLogging(context, buf, length);
    else                         switchToProxying(context);
  }
}

static void logData(Log *context, char* buf, int length) {
  buf[length] = '\0';
  fwrite(buf,sizeof(char),length,context->logFile);
 // fprintf(context->logFile, "%s", buf);
}
